/*
 * Copyright (C) 2011, EADS France
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 */
package xowl.gmi.controller;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import xowl.gmi.environment.Locale;
import xowl.gmi.environment.LongJob;
import xowl.gmi.environment.LongJobHandler;
import xowl.gmi.environment.Options;
import xowl.gmi.view.WaitingWindow;

/**
 *
 * @author Laurent WOUTERS
 */
public class Controller {
    static final String name = "GMI Controller";
    
    private static java.awt.datatransfer.DataFlavor getFlavor(java.lang.Class c) {
        return new java.awt.datatransfer.DataFlavor(java.awt.datatransfer.DataFlavor.javaJVMLocalObjectMimeType + ";class=" + c.getName(), c.getName());
    }
    public static final java.awt.datatransfer.DataFlavor flavorVariable = getFlavor(xowl.gmi.model.cvssl.Variable.class);
    public static final java.awt.datatransfer.DataFlavor flavorASTNode = getFlavor(xowl.gmi.model.ast.ASTNode.class);
    public static final java.awt.datatransfer.DataFlavor flavorASTElementTO = getFlavor(ASTElementTO.class);
    
    private xowl.utils.logging.Logger log;
    // Model data
    private xowl.interpreter.Repository repository;
    private xowl.gmi.environment.DiagramController parent;
    private xowl.lang.owl2.Ontology ontoModel;
    private xowl.lang.owl2.Ontology ontoAST;
    private xowl.lang.owl2.Ontology ontoCommand;
    private xowl.lang.owl2.Ontology ontoMeta;
    private xowl.interpreter.Transformation transfoCM;
    private xowl.interpreter.Transformation transfoMV;
    private xowl.interpreter.Transformation transfoVV;
    private xowl.interpreter.Transformer transformer;
    private CommandFactory commander;
    // DiagramView data
    private xowl.gmi.view.DiagramView view;
    // Sub-controllers
    private Libraries libraries;
    private Variable rootMainView;
    // Action data (selecting, connecting)
    private ASTElement selection;
    private xowl.gmi.model.cvssl.Connector connector;
    private xowl.gmi.view.ToolboxElementSelectable connectingToolbox;
    private GraphNodePort connectionStart;
    
    public xowl.utils.logging.Logger getLog() { return log; }
    public Options getOptions() { return parent.getOptions(); }
    public Locale getLocale() { return parent.getLocale(); }
    public xowl.interpreter.Repository getRepository() { return repository; }
    public xowl.lang.owl2.Ontology getOntologyAST() { return ontoAST; }
    public xowl.lang.owl2.Ontology getOntologyView() { return parent.getModel().getWBView().getOntology(); }
    public xowl.gmi.view.DiagramView getView() { return view; }
    public xowl.gmi.view.ViewStyle getStyle() { return view.getStyle(); }
    public CommandFactory getCommandBuilder() { return commander; }
    public List<xowl.gmi.model.ExecutionEntry> getExecEntriesFor(String symbol) {
        List<xowl.gmi.model.ExecutionEntry> result = new ArrayList<>();
        for (xowl.gmi.model.ExecutionEntry entry : parent.getModel().getWBView().getExecEntries())
            if (entry.getSymbolIRI().equals(symbol))
                result.add(entry);
        return result;
    }
    
    public static Controller newController(final xowl.utils.logging.Logger log, final xowl.lang.owl2.Ontology model, final xowl.gmi.environment.DiagramController diagram) {
        LongJob job = new LongJob() {
            @Override public String getMessage() { return diagram.getLocale().ctrlLoadMessage; }
            @Override public boolean canCancel() { return false; }
            @Override public Object run(LongJobHandler handler) {
                xowl.gmi.controller.Controller controller = new xowl.gmi.controller.Controller(log, model, diagram);
                handler.output(diagram.getLocale().ctrlLoadInitOnto);
                controller.initOntologies();
                handler.output(diagram.getLocale().ctrlLoadInitToolboxes);
                controller.initToolboxes();
                handler.output(diagram.getLocale().ctrlLoadInitView);
                controller.initBuildView();
                handler.output(diagram.getLocale().ctrlLoadInitLibs);
                controller.initBuildLibraries();
                return controller;
            }
            @Override public void requestCancel() { }
        };
        WaitingWindow win = new WaitingWindow(diagram.getLocale(), job);
        return (xowl.gmi.controller.Controller)win.show();
    }
    private Controller(xowl.utils.logging.Logger log, xowl.lang.owl2.Ontology model, xowl.gmi.environment.DiagramController diagram) {
        this.log = log;
        this.repository = diagram.getRepository();
        this.parent = diagram;
        this.ontoModel = model;
        this.view = new xowl.gmi.view.DiagramView(new xowl.gmi.view.DiagramView.Actions() {
            @Override public void onRemoved() { Controller.this.parent.onRemoved(); }
        }, null, diagram.getLocale());
    }
    private void initOntologies() {
        String uriCore = "http://www.xowl.org/gmi/workspace/temp/" + Integer.toString(this.hashCode()) + "/";        
        this.ontoAST = repository.resolveOntology(uriCore + "ast");
        this.ontoCommand = repository.resolveOntology(uriCore + "command");
        this.ontoMeta = repository.resolveOntology(uriCore + "meta");
        this.transfoCM = new xowl.interpreter.Transformation(ontoCommand, ontoModel, ontoMeta, parent.getModel().getWBView().getCMRuleset());
        this.transfoMV = new xowl.interpreter.Transformation(ontoModel, ontoAST, ontoMeta, parent.getModel().getWBView().getMVRuleset());
        this.transfoVV = new xowl.interpreter.Transformation(ontoAST, ontoAST, ontoMeta, parent.getModel().getWBView().getVVRuleset());
        this.transformer = repository.createTransformer(transfoCM, true);
        this.commander = new CommandFactory(log, repository, ontoCommand, transformer);
        repository.apply(transfoMV);
        repository.apply(transfoVV);
    }
    private void initToolboxes() {
        List<xowl.gmi.model.cvssl.Toolbox> toolboxes = new ArrayList<>(parent.getModel().getWBView().getDefinition().getAllToolboxes());
        Collections.sort(toolboxes, new Comparator<xowl.gmi.model.cvssl.Toolbox>() {
            public int compare(xowl.gmi.model.cvssl.Toolbox o1, xowl.gmi.model.cvssl.Toolbox o2) {
                return o1.getIndex() - o2.getIndex();
            }
        });
        for (xowl.gmi.model.cvssl.Toolbox toolbox : toolboxes) {
            List<xowl.gmi.model.cvssl.ToolboxElement> elements = new ArrayList<>(toolbox.getAllComponents());
            Collections.sort(elements, new Comparator<xowl.gmi.model.cvssl.ToolboxElement>() {
                public int compare(xowl.gmi.model.cvssl.ToolboxElement o1, xowl.gmi.model.cvssl.ToolboxElement o2) {
                    return o1.getIndex() - o2.getIndex();
                }
            });
            xowl.gmi.view.Toolbox boxView = new xowl.gmi.view.Toolbox(toolbox.getDescription());
            for (final xowl.gmi.model.cvssl.ToolboxElement boxElem : elements)
                boxView.addToolboxElement(getWidgetFor(boxElem));
            this.view.addToolboxPart(boxView);
        }
    }
    private void initBuildView() {
        xowl.gmi.model.ast.ASTNode root = null;
        if (parent.getModel().getRoot() != null) {
            root = getNodeFor(parent.getModel().getRoot());
        } else {
            String iri = getNewNodeIRI();
            Command command = commander.create(repository.getIRI(iri), parent.getModel().getWBView().getDefinition().getAxiom().getEntity().getHasIRI());
            command.execute();
            root = xowl.gmi.model.ast.ASTNode.get(repository, iri);
        }
        xowl.gmi.model.cvssl.VariableRef ref = xowl.gmi.model.cvssl.VariableRef.instantiate(repository, ontoCommand);
        ref.setVariable(parent.getModel().getWBView().getDefinition().getAxiom());
        this.rootMainView = (Variable)ASTMatcher.buildControlTree(this, null, ref, root, true);
        this.view.setDiagramRoot(rootMainView.getWidget());
        for (xowl.gmi.model.DiagramElement de : parent.getModel().getElements()) {
            if (de instanceof xowl.gmi.model.DiagramNode)
                loadDiagramNode((xowl.gmi.model.DiagramNode)de);
            else if (de instanceof xowl.gmi.model.DiagramLink)
                loadDiagramLink((xowl.gmi.model.DiagramLink)de);
            else if (de instanceof xowl.gmi.model.DiagramRelation)
                loadDiagramRelation((xowl.gmi.model.DiagramRelation)de);
        }
    }
    private void initBuildLibraries() {
        this.libraries = new Libraries(this, parent.getModel().getWBView().getDefinition());
        this.view.addToolboxPart(this.libraries.getView());
    }
    private void loadDiagramNode(xowl.gmi.model.DiagramNode dn) {
        xowl.gmi.model.ast.ASTNode nc = getNodeFor(dn.getObjectIRI());
        xowl.gmi.model.ast.ASTNode np = getNodeFor(dn.getParentIRI());
        if (nc == null || np == null) {
            log.error(name, "Failed to load diagram element " + dn.getID() + " for diagram " + parent.getTitle());
            return;
        }
        for (xowl.gmi.model.ast.Decorator decorator : nc.getAllDecorators()) {
            if (decorator.getParent() == np) {
                decorator.setPositionX(dn.getX());
                decorator.setPositionY(dn.getY());
                decorator.setIndex(dn.getLayer());
                return;
            }
        }
    }
    private void loadDiagramRelation(xowl.gmi.model.DiagramRelation dr) {
        xowl.gmi.model.ast.ASTNode nc = getNodeFor(dr.getObjectIRI());
        xowl.gmi.model.ast.ASTNode np = getNodeFor(dr.getParentIRI());
        if (nc == null || np == null) {
            log.error(name, "Failed to load diagram element " + dr.getID() + " for diagram " + parent.getTitle());
            return;
        }
        for (xowl.gmi.model.ast.Decorator decorator : nc.getAllDecorators()) {
            if (decorator.getParent() == np) {
                decorator.setOriginPort(dr.getOriginPort());
                decorator.setTargetPort(dr.getTargetPort());
                return;
            }
        }
    }
    private void loadDiagramLink(xowl.gmi.model.DiagramLink dl) {
        xowl.gmi.model.ast.ASTNode np = getNodeFor(dl.getParentIRI());
        xowl.gmi.model.ast.ASTNode nc = getNodeFor(dl.getLeftIRI(), dl.getRightIRI(), dl.getPropertyIRI());
        if (nc == null || np == null) {
            log.error(name, "Failed to load diagram element " + dl.getID() + " for diagram " + parent.getTitle());
            return;
        }
        for (xowl.gmi.model.ast.Decorator decorator : nc.getAllDecorators()) {
            if (decorator.getParent() == np) {
                decorator.setOriginPort(dl.getOriginPort());
                decorator.setTargetPort(dl.getTargetPort());
                return;
            }
        }
    }
    private xowl.gmi.model.ast.ASTNode getNodeFor(String modelIRI) {
        xowl.lang.actions.QueryVariable var = new xowl.lang.actions.QueryVariable();
        var.setName("n");
        xowl.lang.owl2.ObjectPropertyAssertion axiom = new xowl.lang.owl2.ObjectPropertyAssertion();
        axiom.setObjectProperty(commander.getMetaTrace().getHasIRI());
        axiom.setIndividual(var);
        axiom.setValueIndividual(repository.getIRI(modelIRI));
        xowl.lang.actions.ValueOf query = new xowl.lang.actions.ValueOf();
        query.addAxioms(axiom);
        query.setOn(var);
        try {
            Collection<xowl.interpreter.QuerySolution> solutions = repository.select(query);
            for (xowl.interpreter.QuerySolution solution : solutions) {
                xowl.gmi.model.ast.ASTNode result = getNodeFor(solution.getValue(var));
                if (result != null)
                    return result;
            }
        } catch (xowl.interpreter.XOWLException ex) { }
        return null;
    }
    private xowl.gmi.model.ast.ASTNode getNodeFor(String left, String right, String property) {
        xowl.lang.actions.QueryVariable n = new xowl.lang.actions.QueryVariable();
        n.setName("n");
        xowl.lang.actions.QueryVariable t = new xowl.lang.actions.QueryVariable();
        t.setName("t");
        
        xowl.lang.owl2.ObjectPropertyAssertion axiom1 = new xowl.lang.owl2.ObjectPropertyAssertion();
        axiom1.setObjectProperty(commander.getMetaOrigin().getHasIRI());
        axiom1.setIndividual(t);
        axiom1.setValueIndividual(repository.getIRI(left));
        
        xowl.lang.owl2.ObjectPropertyAssertion axiom2 = new xowl.lang.owl2.ObjectPropertyAssertion();
        axiom2.setObjectProperty(commander.getMetaTarget().getHasIRI());
        axiom2.setIndividual(t);
        axiom2.setValueIndividual(repository.getIRI(right));
        
        xowl.lang.owl2.ObjectPropertyAssertion axiom3 = new xowl.lang.owl2.ObjectPropertyAssertion();
        axiom3.setObjectProperty(commander.getMetaProperty().getHasIRI());
        axiom3.setIndividual(t);
        axiom3.setValueIndividual(repository.getIRI(property));
        
        xowl.lang.owl2.ObjectPropertyAssertion axiom4 = new xowl.lang.owl2.ObjectPropertyAssertion();
        axiom4.setObjectProperty(commander.getMetaConnector().getHasIRI());
        axiom4.setIndividual(t);
        axiom4.setValueIndividual(n);
        
        xowl.lang.actions.ValueOf query = new xowl.lang.actions.ValueOf();
        query.addAxioms(axiom1);
        query.addAxioms(axiom2);
        query.addAxioms(axiom3);
        query.addAxioms(axiom4);
        query.setOn(n);
        try {
            Collection<xowl.interpreter.QuerySolution> solutions = repository.select(query);
            for (xowl.interpreter.QuerySolution solution : solutions) {
                xowl.gmi.model.ast.ASTNode result = getNodeFor(solution.getValue(n));
                if (result != null)
                    return result;
            }
        } catch (xowl.interpreter.XOWLException ex) { }
        return null;
    }
    private xowl.gmi.model.ast.ASTNode getNodeFor(xowl.lang.runtime.Value value) {
        if (value == null)
            return null;
        if (value instanceof xowl.lang.runtime.Entity) {
            xowl.lang.runtime.Entity entity = (xowl.lang.runtime.Entity)value;
            if (entity.getContainedBy() == ontoAST)
                return (xowl.gmi.model.ast.ASTNode.get(repository, entity));
            return null;
        }
        if (value instanceof xowl.lang.runtime.Array) {
            xowl.lang.runtime.Array array = (xowl.lang.runtime.Array)value;
            for (xowl.lang.runtime.Element item : array.getAllElements()) {
                xowl.gmi.model.ast.ASTNode temp = getNodeFor(item.getValue());
                if (temp != null)
                    return temp;
            }
        }
        return null;
    }
    
    private xowl.gmi.view.ToolboxElement getWidgetFor(xowl.gmi.model.cvssl.ToolboxElement boxElem) {
        xowl.gmi.model.cvssl.Symbol symbol = boxElem.getSymbol();
        if (symbol instanceof xowl.gmi.model.cvssl.Variable)
            return getWidgetFor(boxElem, (xowl.gmi.model.cvssl.Variable)symbol);
        return getWidgetFor(boxElem, (xowl.gmi.model.cvssl.Connector)symbol);
    }
    private xowl.gmi.view.ToolboxElement getWidgetFor(xowl.gmi.model.cvssl.ToolboxElement boxElem, final xowl.gmi.model.cvssl.Variable variable) {
        xowl.gmi.view.ToolboxElementDragable.Actions actions = new xowl.gmi.view.ToolboxElementDragable.Actions() {
            @Override public java.awt.datatransfer.DataFlavor getFlavor() { return flavorVariable; }
            @Override public Object getData() { return variable; }
        };
        return new xowl.gmi.view.ToolboxElementDragable(actions, boxElem.getDescription(), view.getStyle());
    }
    private xowl.gmi.view.ToolboxElement getWidgetFor(xowl.gmi.model.cvssl.ToolboxElement boxElem, final xowl.gmi.model.cvssl.Connector connector) {
        xowl.gmi.view.ToolboxElementSelectable.Actions actions = new xowl.gmi.view.ToolboxElementSelectable.Actions() {
            @Override public void select(xowl.gmi.view.ToolboxElementSelectable selectable) {
                connectInit(connector, selectable);
            }
        };
        return new xowl.gmi.view.ToolboxElementSelectable(actions, boxElem.getDescription(), view.getStyle());
    }
    
    public String getNewNodeIRI() {
        java.util.Random rand = new java.util.Random();
        return this.ontoAST.getHasIRI().getHasValue() + "#Node" + Integer.toHexString(rand.nextInt());
    }
    
    public void executeUIAction(DiagramUIAction action) {
        action.setDiagram(parent);
        parent.executeUIAction(action);
    }
    
    public void execute(xowl.lang.owl2.IRI object, String eeid) { parent.execute(object, eeid); }
    
    public void onDiagramUpdated() {
        view.onDiagramUpdated();
    }
    
    public void setSelection(ASTElement selection) { this.selection = selection; }
    
    public void connectInit(xowl.gmi.model.cvssl.Connector connector, xowl.gmi.view.ToolboxElementSelectable selectable) {
        if (this.connector != null) {
            connectingToolbox.toggleConnecting();
            this.connector = connector;
            connectingToolbox = selectable;
            connectingToolbox.toggleConnecting();
        } else {
            this.connector = connector;
            connectingToolbox = selectable;
            connectingToolbox.toggleConnecting();
            view.beginConnecting();
            view.getRootPane().getInputMap(javax.swing.JComponent.WHEN_IN_FOCUSED_WINDOW).put(javax.swing.KeyStroke.getKeyStroke(java.awt.event.KeyEvent.VK_ESCAPE, 0), "Cancel");
            view.getRootPane().getActionMap().put("Cancel", new javax.swing.AbstractAction() {
                @Override public void actionPerformed(java.awt.event.ActionEvent ae) {
                    view.endConnecting();
                    connectingToolbox.toggleConnecting();
                    connectingToolbox = null;
                    Controller.this.connector = null;
                }
            });
        }
    }
    public boolean connectCanStartAt(GraphNode node) {
        Collection<xowl.gmi.model.cvssl.Port> ports1 = node.getDefinition().getAllPorts();
        for (xowl.gmi.model.cvssl.ConnectorPort cp : connector.getOrigin().getAllEndPorts())
            if (ports1.contains(cp.getPort()))
                return true;
        return false;
    }
    public boolean connectCanEndAt(GraphNode node) {
        Collection<xowl.gmi.model.cvssl.Port> ports1 = node.getDefinition().getAllPorts();
        for (xowl.gmi.model.cvssl.ConnectorPort cp : connector.getTarget().getAllEndPorts())
            if (ports1.contains(cp.getPort()))
                return true;
        return false;
    }
    public void connectStart(GraphNodePort port) { connectionStart = port; }
    public void connectEnd(GraphNodePort port) {
        port.getGraph().actionConnect(connector, connectionStart, port);
        connectingToolbox.toggleConnecting();
        connectingToolbox = null;
        connector = null;
        view.onDiagramUpdated();
    }
    
    public void onEditCut() { }
    public void onEditCopy() { }
    public void onEditPaste() { }
    public void onEditRemove() { }
    public void onEditDelete() { }
    
    public void save() {
        //parent.getModel().clear();
        //parent.getModel().setRoot(rootMainView.getModelIRI().getHasValue());
        //rootMainView.saveTo(parent.getModel());
    }
    
    public void cleanup() {
        rootMainView.decommission();
        transformer.delete();
        repository.unapply(transfoMV);
        repository.unapply(transfoVV);
        repository.dropOntology(ontoCommand);
        repository.dropOntology(ontoAST);
        repository.dropOntology(ontoMeta);
    }
    
    public java.awt.image.BufferedImage exportBMP() { return view.exportBMP(); }
    public org.w3c.dom.Document exportSVG() { return view.exportSVG(); }
}
